package com.minsf.rpc.spi;

import cn.hutool.core.convert.CastUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import com.minsf.rpc.fault.retry.RetryStrategy;
import com.minsf.rpc.loadbalancer.LoadBalancer;
import com.minsf.rpc.registry.Registry;
import com.minsf.rpc.serializer.Serializer;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description
 * @Author minify
 * @Date 2024/3/26 21:52
 * @Project sf-rpc
 */
@Slf4j
public class SpiLoader {


    /**
     * 存储已加载的类   Serializer.class：（jdk:JdkSerializer.class）
     */
    private static Map<String, Map<String, Class<?>>> loaderMap = new ConcurrentHashMap<>();

    /**
     * 对象实例缓存  类路径：实例  单例模式
     */
    private static Map<String ,Object> instanceCache = new ConcurrentHashMap<>();

    private static final String PRC_SYSTEM_SPI_DIR = "META-INF"+ File.separator +"rpc"+File.separator+"system"+File.separator;

    private static final String PRC_CUSTOM_SPI_DIR = "META-INF"+ File.separator +"rpc"+File.separator+"custom"+File.separator;

    /**
     * 扫描路径
     */
    private static final String[] SCAN_DIRS = new String[]{ PRC_SYSTEM_SPI_DIR, PRC_CUSTOM_SPI_DIR };

    /**
     * 需要加载的类列表
     */
    private static final List<Class<?>> LOAD_CLASS_LIST = Arrays.asList(Serializer.class, Registry.class, LoadBalancer.class, RetryStrategy.class);

    /**
     * 加载所有的类
     */
    public static void loadAll() {
        for (Class<?> aClass : LOAD_CLASS_LIST) {
            load(aClass);
        }
    }

    /**
     * 按类型加载 所有实现类到map
     * @param aClass
     * @return
     */
    public static Map<String, Class<?>> load(Class<?> aClass) {
        String needLoadType = aClass.getName();
        log.info("加载类型为 {} 的 SPI ", needLoadType);

        Map<String, Class<?>> keyClassMap = new HashMap<>();

        if (!LOAD_CLASS_LIST.contains(aClass)) {
            log.error("不存在需要加载的类 {}", aClass.getName());
            throw new RuntimeException("不存在需要加载的类");
        }

        // 加载所有目录下的类
        for (String scanDir : SCAN_DIRS) {

            List<URL> resources = ResourceUtil.getResources(scanDir + aClass.getName());
            // 读取每个资源文件
            for (URL resource : resources) {

                try {
                    InputStreamReader inputStreamReader = new InputStreamReader(resource.openStream());
                    BufferedReader bufferedInputStream = new BufferedReader(inputStreamReader);
                    String line;
                    while ((line = bufferedInputStream.readLine()) != null) {
                        String[] split = line.split("=");
                        String key = split[0];
                        String classPath = split[1];
                        keyClassMap.put(key, Class.forName(classPath));
                    }

                } catch (IOException | ClassNotFoundException e) {
                    log.error("load error for {}", e.getMessage());
                }

            }

        }
        loaderMap.put(aClass.getName(), keyClassMap);
        return keyClassMap;
    }

    /**
     * 根据某一类型加载某一具体实例
     * @param loadClass 类型，如Serializer.class
     * @param key 实例对应的key 如 json、kryo
     * @return
     * @param <T>
     */
    public static  <T> T getInstance(Class<T> loadClass ,String key) {
        String name = loadClass.getName();
        log.info("获取类型 {} 的实例 {}", loadClass.getName(), key);
        Map<String, Class<?>> keyClassMap = loaderMap.get(name);
        if (keyClassMap == null) {
            throw new RuntimeException("不存在所加载的类");
        }

        if (!keyClassMap.containsKey(key)) {
            log.error("{} 类型不存在 {}", name, key);
            throw new RuntimeException("类型不存在"+name+" "+key);
        }

        // 没有这个实例就加载一个
        Class<?> instanceKey = keyClassMap.get(key);
        String instanceKeyName = instanceKey.getName();
        // DCL 懒加载创建实例，并存入缓存
        if (!instanceCache.containsKey(instanceKeyName)) {
            synchronized (SpiLoader.class) {
                if (!instanceCache.containsKey(instanceKeyName)) {
                    try {
                        instanceCache.put(instanceKeyName, instanceKey.newInstance());
                    } catch (InstantiationException | IllegalAccessException e) {
                        log.error("实例化失败：{}", instanceKeyName);
                        throw new RuntimeException(e);
                    }
                }
            }
        }

        return (T) instanceCache.get(instanceKeyName);
    }
}
